package com.hra.camera2demo;

import static com.hra.camera2demo.Camera2ConfigManager.KEY_CAPTURE_SIZE;
import static com.hra.camera2demo.Camera2ConfigManager.KEY_FOCUS_MODE;
import static com.hra.camera2demo.Camera2ConfigManager.KEY_PREVIEW_SIZE;
import static com.hra.camera2demo.Camera2ConfigManager.KEY_SCENE_MODE;
import static com.hra.camera2demo.Camera2ConfigManager.KEY_VIDEO_SIZE;
import static com.hra.camera2demo.Camera2ConfigManager.KEY_WHITE_BALANCE;

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.ImageFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.MeteringRectangle;
import android.media.CamcorderProfile;
import android.media.Image;
import android.media.ImageReader;
import android.media.MediaMetadataRetriever;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivityDebug";
    private static final int PERMISSION_REQUEST_CODE = 0x100;

    private static final String DATA_FORMAT = "mm:ss";
    private static final String IMAGE_FORMAT = "'IMG'_yyyyMMdd_HHmmss_SSS";
    private static final String VIDEO_FORMAT = "'VID'_yyyyMMdd_HHmmss_SSS";
    private static final float AF_REGION_BOX = 0.15f;
    private static final float REGION_WEIGHT = 0.022f;
    private static final int CAMERA2_REGION_WEIGHT = (int) (CoordinatesTransform.lerp(MeteringRectangle.METERING_WEIGHT_MIN, MeteringRectangle.METERING_WEIGHT_MAX, REGION_WEIGHT));

    private Button btn_scene;
    private Button btn_fm;
    private Button btn_wb;
    private Button btn_ps;
    private Button btn_is;
    private Button btn_vs;
    private Button btn_switch_mode;
    private Button btn_switch_camera;
    private Button btn_capture;

    private TextureView mTextureView;
    private SharedPreferences mPreferences;
    private View focusIndicator;
    private TextView recordingTime;

    private final Handler mainHandler = new Handler(Looper.getMainLooper());
    private Handler backgroundHandler;
    private HandlerThread backgroundThread;

    private String mCameraID = "0";
    private CameraManager mCameraManager;
    private CameraDevice mCameraDevice;
    private CameraCharacteristics mCharacteristics = null;
    private ImageReader mCaptureImageReader;
    private CameraCaptureSession mCameraCaptureSession;
    private MediaRecorder mMediaRecorder;

    private float touchX, touchY;
    private boolean isVideoMode = false;
    private boolean isRecording = false;
    private Uri mCurrentVideoUri = null;
    private long mCurrentVideoDuration = 0L;
    private Runnable recordingDurationRun;

    private Camera2ConfigManager mConfigManager;

    private static final SparseIntArray TEXTURE_ORIENTATION = new SparseIntArray();
    private static final SparseIntArray JPEG_ORIENTATION = new SparseIntArray();
    private static final SparseIntArray VIDEO_ORIENTATION = new SparseIntArray();

    static {
        TEXTURE_ORIENTATION.append(0, 0);
        TEXTURE_ORIENTATION.append(90, 270);
        TEXTURE_ORIENTATION.append(180, 180);
        TEXTURE_ORIENTATION.append(270, 90);

        JPEG_ORIENTATION.append(0, 90);
        JPEG_ORIENTATION.append(90, 0);
        JPEG_ORIENTATION.append(180, 270);
        JPEG_ORIENTATION.append(270, 180);

        VIDEO_ORIENTATION.append(0, 90);
        VIDEO_ORIENTATION.append(90, 0);
        VIDEO_ORIENTATION.append(180, 270);
        VIDEO_ORIENTATION.append(270, 180);
    }

    private final TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            openCamera();
        }

        @Override
        public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {

        }

        @Override
        public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
            closeCamera();
            return true;
        }

        @Override
        public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {

        }
    };

    private final CameraCaptureSession.CaptureCallback previewCallback = new CameraCaptureSession.CaptureCallback() {
        @Override
        public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
            Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
            if (afState == null) return;
            switch (afState) {
                case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED:
                    // 对焦成功
                    showFocusSuccess();
                    updatePreview(session);
                    break;
                case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
                    // 对焦失败
                    showFocusFailed();
                    updatePreview(session);
                    break;
            }
        }
    };

    private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            Log.i(TAG, "CameraDevice stateCallback onOpened camera=" + camera);
            mCameraDevice = camera;
            setCaptureImageReader();
            startPreview();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            Log.i(TAG, "CameraDevice stateCallback onDisconnected camera=" + camera);
            closeCamera();
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            Log.i(TAG, "CameraDevice stateCallback onError camera=" + camera + ", error=" + error);
            closeCamera();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        String[] requiredPermissions = {
                Manifest.permission.CAMERA,
                Manifest.permission.RECORD_AUDIO
        };
        PermissionsUtil.checkAndRequestPermissions(this, requiredPermissions, PERMISSION_REQUEST_CODE);

        mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
        mPreferences = getSharedPreferences("camera_settings", MODE_PRIVATE);
        mConfigManager = new Camera2ConfigManager(this, mPreferences);

        try {
            String[] cameraIdList = mCameraManager.getCameraIdList();
            if (cameraIdList.length <= 0) {
                Log.e("Camera", "no Camera");
                finish();
                return;
            } else {
                mCameraID = cameraIdList[0];
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

        recordingTime = findViewById(R.id.recording_time);
        focusIndicator = findViewById(R.id.focus_indicator);
        mTextureView = findViewById(R.id.texture_view);
        btn_scene = findViewById(R.id.scene_mode);
        btn_fm = findViewById(R.id.focus_mode);
        btn_wb = findViewById(R.id.white_balance);
        btn_ps = findViewById(R.id.preview_size);
        btn_is = findViewById(R.id.capture_size);
        btn_vs = findViewById(R.id.video_size);
        btn_switch_mode = findViewById(R.id.switch_mode);
        btn_switch_camera = findViewById(R.id.switch_camera);
        btn_capture = findViewById(R.id.btn_capture);

        recordingDurationRun = () -> {
            if (isRecording) {
                mCurrentVideoDuration += 1000;
                @SuppressLint("SimpleDateFormat") String duration = new SimpleDateFormat(DATA_FORMAT).format(new Date(mCurrentVideoDuration));
                recordingTime.setText(duration);
                mainHandler.postDelayed(recordingDurationRun, 1000);
            }
        };
        updateUI();
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mTextureView.isAvailable()) {
            startBackgroundThread();
            openCamera();
        } else {
            mTextureView.setSurfaceTextureListener(textureListener);
        }
    }

    @Override
    protected void onPause() {
        closeCamera();
        stopBackgroundThread();
        super.onPause();
    }

    @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (mTextureView == null || mTextureView.getParent() == null) return;
        ViewGroup parent = (ViewGroup) mTextureView.getParent();
        parent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                parent.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                updateTextureSize();
            }
        });
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == PERMISSION_REQUEST_CODE) {
            PermissionsUtil.checkAuthorized(this, permissions, grantResults);
        }
    }

    private void handleFocus(float x, float y) {
        showFocusIndicator();
        if (mCharacteristics == null || mCameraCaptureSession == null || mCameraDevice == null) {
            return;
        }
        int displayOrientation = CoordinatesTransform.getDisplayRotation(this);
        Rect cropRegion = mCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);

        DisplayMetrics dm = getResources().getDisplayMetrics();
        int displayWidth = dm.widthPixels;
        int displayHeight = dm.heightPixels;
        int left = (displayWidth - mTextureView.getHeight()) / 2;
        int top = (displayHeight - mTextureView.getWidth()) / 2 - 30;
        Rect previewArea = new Rect(left, top, left + mTextureView.getHeight(), top + mTextureView.getWidth());

        Rect focusRect = CoordinatesTransform.uiToSensor(
                new Point((int) x, (int) y),
                previewArea,
                displayOrientation,
                AF_REGION_BOX,
                cropRegion,
                mCharacteristics);
        MeteringRectangle focusArea = new MeteringRectangle(focusRect, CAMERA2_REGION_WEIGHT);
        Log.d(TAG, "handleFocus: focusArea=" + focusArea);

        try {
            // 创建独立的对焦请求（不修改预览会话）
            CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            builder.addTarget(new Surface(mTextureView.getSurfaceTexture()));

            // 设置参数
            builder.set(CaptureRequest.CONTROL_AF_REGIONS, new MeteringRectangle[]{focusArea});
            builder.set(CaptureRequest.CONTROL_AE_REGIONS, new MeteringRectangle[]{focusArea});
            builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
            builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
            builder.set(CaptureRequest.CONTROL_AWB_MODE, mConfigManager.whiteBalanceMode);
            builder.set(CaptureRequest.CONTROL_SCENE_MODE, mConfigManager.sceneMode);
            builder.set(CaptureRequest.CONTROL_MODE,
                    mConfigManager.sceneMode == CaptureRequest.CONTROL_SCENE_MODE_DISABLED
                            ? CaptureRequest.CONTROL_MODE_AUTO
                            : CaptureRequest.CONTROL_MODE_USE_SCENE_MODE);

            mCameraCaptureSession.setRepeatingRequest(builder.build(), previewCallback, backgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void showFocusIndicator() {
        runOnUiThread(() -> {
            focusIndicator.setBackgroundResource(R.drawable.ic_focus_default);
            focusIndicator.setX((float) (touchX - focusIndicator.getWidth() / 2.0));
            focusIndicator.setY((float) (touchY - focusIndicator.getHeight() / 2.0 - 40));
            focusIndicator.setVisibility(View.VISIBLE);
            new Handler(Looper.getMainLooper()).postDelayed(() ->
                    focusIndicator.setVisibility(View.GONE), 3000);
        });
    }

    private void showFocusSuccess() {
        runOnUiThread(() -> {
            focusIndicator.setBackgroundResource(R.drawable.ic_focus_success);
            focusIndicator.setVisibility(View.VISIBLE);
            new Handler(Looper.getMainLooper()).postDelayed(() ->
                    focusIndicator.setVisibility(View.GONE), 500);
        });
    }

    private void showFocusFailed() {
        runOnUiThread(() -> {
            focusIndicator.setBackgroundResource(R.drawable.ic_focus_failed);
            focusIndicator.setVisibility(View.VISIBLE);
            new Handler(Looper.getMainLooper()).postDelayed(() ->
                    focusIndicator.setVisibility(View.GONE), 500);
        });
    }

    private void updateSceneModeRestriction() {
        if (mConfigManager.sceneMode == CameraCharacteristics.CONTROL_SCENE_MODE_SUNSET
                || mConfigManager.sceneMode == CameraCharacteristics.CONTROL_SCENE_MODE_LANDSCAPE) {
            mConfigManager.whiteBalanceMode = CameraCharacteristics.CONTROL_AWB_MODE_DAYLIGHT;
            btn_wb.setVisibility(View.GONE);
        } else if (mConfigManager.sceneMode == CameraCharacteristics.CONTROL_SCENE_MODE_CANDLELIGHT) {
            mConfigManager.whiteBalanceMode = CameraCharacteristics.CONTROL_AWB_MODE_INCANDESCENT;
            btn_wb.setVisibility(View.GONE);
        } else {
            mConfigManager.whiteBalanceMode = CameraCharacteristics.CONTROL_AWB_MODE_AUTO;
            btn_wb.setVisibility(View.VISIBLE);
        }
        mConfigManager.whiteBalanceModeIndex = Camera2ConfigHelper.getIndex(mConfigManager.supportedWhiteBalanceModes, mConfigManager.whiteBalanceMode);
    }

    @SuppressLint({"SetTextI18n", "ClickableViewAccessibility"})
    private void initConfigButtons() {
        if (mCharacteristics == null) return;
        mConfigManager.initConfigs(mCharacteristics, mCameraID);
        updateSceneModeRestriction();
        // Scene mode
        String[] sceneModeNames = Camera2ConfigHelper.getSceneModeNames(mConfigManager.supportedSceneModes);
        btn_scene.setText(getString(R.string.scene_mode) + "\n" + sceneModeNames[mConfigManager.sceneModeIndex]);
        btn_scene.setOnClickListener(v -> mConfigManager.showConfigDialog(
                getString(R.string.scene_mode),
                sceneModeNames,
                mConfigManager.sceneModeIndex,
                which -> {
                    mConfigManager.sceneModeIndex = which;
                    mConfigManager.sceneMode = mConfigManager.supportedSceneModes[which];
                    mPreferences.edit().putInt(KEY_SCENE_MODE, mConfigManager.sceneMode).apply();
                    btn_scene.setText(getString(R.string.scene_mode) + "\n" + sceneModeNames[which]);
                    updateSceneModeRestriction();
                    updatePreview(mCameraCaptureSession);
                }
        ));

        // Focus mode
        String[] focusModeNames = Camera2ConfigHelper.getFocusModeNames(mConfigManager.supportedFocusModes);
        btn_fm.setText(getString(R.string.focus_modes) + "\n" + focusModeNames[mConfigManager.focusModeIndex]);
        btn_fm.setOnClickListener(v -> mConfigManager.showConfigDialog(
                getString(R.string.focus_modes),
                focusModeNames,
                mConfigManager.focusModeIndex,
                which -> {
                    mConfigManager.focusModeIndex = which;
                    mConfigManager.focusMode = mConfigManager.supportedFocusModes[which];
                    mPreferences.edit().putInt(KEY_FOCUS_MODE, mConfigManager.focusMode).apply();
                    btn_fm.setText(getString(R.string.focus_modes) + "\n" + focusModeNames[which]);
                    updatePreview(mCameraCaptureSession);
                }
        ));

        // White Balance
        String[] wbModeNames = Camera2ConfigHelper.getWhiteBalanceModeNames(mConfigManager.supportedWhiteBalanceModes);
        btn_wb.setText(getString(R.string.white_balance) + "\n" + wbModeNames[mConfigManager.whiteBalanceModeIndex]);
        btn_wb.setOnClickListener(v -> mConfigManager.showConfigDialog(
                getString(R.string.white_balance),
                wbModeNames,
                mConfigManager.whiteBalanceModeIndex,
                which -> {
                    mConfigManager.whiteBalanceModeIndex = which;
                    mConfigManager.whiteBalanceMode = mConfigManager.supportedWhiteBalanceModes[which];
                    mPreferences.edit().putInt(KEY_WHITE_BALANCE, mConfigManager.whiteBalanceMode).apply();
                    btn_wb.setText(getString(R.string.white_balance) + "\n" + wbModeNames[which]);
                    updatePreview(mCameraCaptureSession);
                }
        ));

        // Preview size
        String[] previewSizeStrings = Camera2ConfigHelper.getSizeStrings(mConfigManager.supportedPreviewSizes);
        btn_ps.setText(getString(R.string.preview_sizes) + "\n" + previewSizeStrings[mConfigManager.previewSizeIndex]);
        btn_ps.setOnClickListener(v -> mConfigManager.showConfigDialog(
                getString(R.string.preview_sizes),
                previewSizeStrings,
                mConfigManager.previewSizeIndex,
                which -> {
                    mConfigManager.previewSizeIndex = which;
                    mConfigManager.previewSize = mConfigManager.supportedPreviewSizes.get(which);
                    mPreferences.edit().putString(KEY_PREVIEW_SIZE, mConfigManager.previewSize.toString()).apply();
                    btn_ps.setText(getString(R.string.preview_sizes) + "\n" + previewSizeStrings[which]);
                    closeCamera();
                    openCamera();
                }
        ));

        // Capture size
        String[] captureSizeStrings = Camera2ConfigHelper.getSizeStrings(mConfigManager.supportedCaptureSizes);
        btn_is.setText(getString(R.string.capture_sizes) + "\n" + captureSizeStrings[mConfigManager.captureSizeIndex]);
        btn_is.setOnClickListener(v -> mConfigManager.showConfigDialog(
                getString(R.string.capture_sizes),
                captureSizeStrings,
                mConfigManager.captureSizeIndex,
                which -> {
                    mConfigManager.captureSizeIndex = which;
                    mConfigManager.captureSize = mConfigManager.supportedCaptureSizes.get(which);
                    mPreferences.edit().putString(KEY_CAPTURE_SIZE, mConfigManager.captureSize.toString()).apply();
                    btn_is.setText(getString(R.string.capture_sizes) + "\n" + captureSizeStrings[which]);
                    closeCamera();
                    openCamera();
                }
        ));

        // Video size
        String[] videoSizeStrings = Camera2ConfigHelper.getSizeStrings(mConfigManager.supportedVideoSizes);
        btn_vs.setText(getString(R.string.video_quality) + "\n" + videoSizeStrings[mConfigManager.videoSizeIndex]);
        btn_vs.setOnClickListener(v -> mConfigManager.showConfigDialog(
                getString(R.string.video_quality),
                videoSizeStrings,
                mConfigManager.videoSizeIndex,
                which -> {
                    mConfigManager.videoSizeIndex = which;
                    mConfigManager.videoSize = mConfigManager.supportedVideoSizes.get(which);
                    mPreferences.edit().putString(KEY_VIDEO_SIZE, mConfigManager.videoSize.toString()).apply();
                    btn_vs.setText(getString(R.string.video_quality) + "\n" + videoSizeStrings[which]);
                    for (CamcorderProfile profile : mConfigManager.supportedCamcorderProfiles) {
                        if (profile.videoFrameWidth == mConfigManager.videoSize.getWidth() && profile.videoFrameHeight == mConfigManager.videoSize.getHeight()) {
                            mConfigManager.profile = profile;
                            break;
                        }
                    }
                    closeCamera();
                    openCamera();
                }
        ));


        mTextureView.setOnTouchListener((v, event) -> {
            if (mConfigManager.focusMode == CaptureRequest.CONTROL_AF_MODE_AUTO) {
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    touchX = event.getRawX();
                    touchY = event.getRawY();
                    handleFocus(event.getRawX(), event.getRawY());
                }
            }
            return true;
        });
        btn_switch_camera.setOnClickListener(v -> switchCamera());
        btn_switch_mode.setOnClickListener(v -> switchMode());
        btn_capture.setOnClickListener(v -> {
            if (isVideoMode) {
                toggleRecording();
            } else {
                captureStillPicture();
            }
        });
        updateUI();
    }

    private void switchCamera() {
        try {
            if (mCameraManager == null) return;
            String[] cameraIdList = mCameraManager.getCameraIdList();
            // 如果摄像头数量 <= 1，直接返回
            if (cameraIdList.length <= 1) {
                Log.e("Camera", "设备只有一个摄像头，无法切换");
                return;
            }
            // 找到当前摄像头在列表中的索引
            int currentIndex = Arrays.asList(cameraIdList).indexOf(mCameraID);
            if (currentIndex == -1) {
                Log.e("Camera", "当前摄像头 ID 无效");
                return;
            }
            // 切换到下一个摄像头（循环遍历）
            int nextIndex = (currentIndex + 1) % cameraIdList.length;
            mCameraID = cameraIdList[nextIndex];
            // 重启摄像头
            closeCamera();
            openCamera();
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void switchMode() {
        if (isVideoMode) {
            stopRecording();
            isVideoMode = false;
        } else {
            isVideoMode = true;
        }
        closeCamera();
        updateUI();
        openCamera();
    }

    private void startBackgroundThread() {
        backgroundThread = new HandlerThread("CameraBackgroundThread");
        backgroundThread.start();
        backgroundHandler = new Handler(backgroundThread.getLooper());
    }

    private void openCamera() {
        Log.i(TAG, "openCamera start mCameraID=" + mCameraID);
        if (TextUtils.isEmpty(mCameraID)) {
            Log.i(TAG, "openCamera failed because of having none valid mCameraID");
            return;
        }

        try {
            mCharacteristics = mCameraManager.getCameraCharacteristics(mCameraID);
            initConfigButtons();
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                Log.i(TAG, "openCamera return because of having not camera permission");
                Toast.makeText(this, "没有相机权限，请先授权", Toast.LENGTH_SHORT).show();
                return;
            }
            mCameraManager.openCamera(mCameraID, stateCallback, backgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void setCaptureImageReader() {
        if (mCharacteristics == null) {
            Log.w(TAG, "setCaptureImageReader fail because of mCharacteristics == null");
            return;
        }

        mCaptureImageReader = ImageReader.newInstance(mConfigManager.captureSize.getWidth(), mConfigManager.captureSize.getHeight(), ImageFormat.JPEG, 2);
        mCaptureImageReader.setOnImageAvailableListener(reader -> {
            Log.i(TAG, "mCaptureImageReader onImageAvailable callback time: " + System.currentTimeMillis());
            try (Image image = reader.acquireNextImage()) {
                if (image != null) {
                    saveBitmap(image);
                }
            } catch (IllegalStateException e) {
                Log.e(TAG, e.getMessage());
            }
        }, mainHandler);
    }

    private void captureStillPicture() {
        try {
            if (mCameraDevice == null || mCameraCaptureSession == null) return;

            CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            builder.addTarget(mCaptureImageReader.getSurface());

            builder.set(CaptureRequest.CONTROL_AF_MODE, mConfigManager.focusMode);
            builder.set(CaptureRequest.CONTROL_AWB_MODE, mConfigManager.whiteBalanceMode);
            builder.set(CaptureRequest.CONTROL_SCENE_MODE, mConfigManager.sceneMode);
            builder.set(CaptureRequest.CONTROL_MODE,
                    mConfigManager.sceneMode == CaptureRequest.CONTROL_SCENE_MODE_DISABLED
                            ? CaptureRequest.CONTROL_MODE_AUTO
                            : CaptureRequest.CONTROL_MODE_USE_SCENE_MODE);

            int displayOrientation = CoordinatesTransform.getDisplayRotation(this);
            Log.d(TAG, "captureStillPicture: displayOrientation=" + displayOrientation);
            builder.set(CaptureRequest.JPEG_ORIENTATION, JPEG_ORIENTATION.get(displayOrientation));

            mCameraCaptureSession.stopRepeating();
            mCameraCaptureSession.capture(builder.build(), new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                    updatePreview(session);
                }
            }, backgroundHandler);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void startPreview() {
        if (mCameraDevice == null) return;
        try {
            SurfaceTexture texture = mTextureView.getSurfaceTexture();
            if (texture == null) return;
            updateTextureSize();

            texture.setDefaultBufferSize(mConfigManager.previewSize.getWidth(), mConfigManager.previewSize.getHeight());
            Surface previewSurface = new Surface(texture);
            List<Surface> targets = Arrays.asList(previewSurface, mCaptureImageReader.getSurface());

            if (mCameraCaptureSession != null) {
                mCameraCaptureSession.close();
                mCameraCaptureSession = null;
            }
            mCameraDevice.createCaptureSession(targets, new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    mCameraCaptureSession = session;
                    updatePreview(session);
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                }
            }, backgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void updatePreview(CameraCaptureSession session) {
        try {
            if (session == null || mCameraDevice == null) return;
            CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            builder.addTarget(new Surface(mTextureView.getSurfaceTexture()));
            if (mMediaRecorder != null && mMediaRecorder.getSurface() != null) {
                builder.addTarget(mMediaRecorder.getSurface());
            }

            builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
            builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
            builder.set(CaptureRequest.CONTROL_AF_MODE, mConfigManager.focusMode);
            builder.set(CaptureRequest.CONTROL_AWB_MODE, mConfigManager.whiteBalanceMode);
            builder.set(CaptureRequest.CONTROL_SCENE_MODE, mConfigManager.sceneMode);
            builder.set(CaptureRequest.CONTROL_MODE,
                    mConfigManager.sceneMode == CaptureRequest.CONTROL_SCENE_MODE_DISABLED
                            ? CaptureRequest.CONTROL_MODE_AUTO
                            : CaptureRequest.CONTROL_MODE_USE_SCENE_MODE);

            session.setRepeatingRequest(builder.build(), null, backgroundHandler);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void toggleRecording() {
        if (isRecording) {
            stopRecording();
            startPreview();
        } else {
            prepareRecording();
        }
    }

    private void prepareRecording() {
        if (mMediaRecorder == null) {
            mMediaRecorder = new MediaRecorder();
        } else {
            mMediaRecorder.reset();
        }

        try {
            if (mConfigManager.profile == null) return;

            mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
            mMediaRecorder.setVideoSize(mConfigManager.profile.videoFrameWidth, mConfigManager.profile.videoFrameHeight);
            mMediaRecorder.setVideoEncodingBitRate(mConfigManager.profile.videoBitRate);
            mMediaRecorder.setVideoFrameRate(mConfigManager.profile.videoFrameRate);
            mMediaRecorder.setAudioEncodingBitRate(mConfigManager.profile.audioBitRate);
            mMediaRecorder.setAudioSamplingRate(mConfigManager.profile.audioSampleRate);
            mMediaRecorder.setVideoEncoder(mConfigManager.profile.videoCodec);
            mMediaRecorder.setAudioEncoder(mConfigManager.profile.audioCodec);
            mMediaRecorder.setOutputFile(getOutputFile());
            int displayOrientation = CoordinatesTransform.getDisplayRotation(this);
            Log.d(TAG, "prepareRecording: displayOrientation=" + displayOrientation);
            mMediaRecorder.setOrientationHint(VIDEO_ORIENTATION.get(displayOrientation));
            mMediaRecorder.prepare();

            // 创建新的CaptureSession包含MediaRecorder的Surface
            SurfaceTexture texture = mTextureView.getSurfaceTexture();
            texture.setDefaultBufferSize(mConfigManager.previewSize.getWidth(), mConfigManager.previewSize.getHeight());
            Surface previewSurface = new Surface(texture);
            Surface recorderSurface = mMediaRecorder.getSurface();

            if (mCameraCaptureSession != null) {
                mCameraCaptureSession.close();
                mCameraCaptureSession = null;
            }
            mCameraDevice.createCaptureSession(
                    Arrays.asList(previewSurface, recorderSurface),
                    new CameraCaptureSession.StateCallback() {
                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession session) {
                            mCameraCaptureSession = session;
                            updatePreview(mCameraCaptureSession);
                            startRecording();
                        }

                        @Override
                        public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                        }
                    }, backgroundHandler);
        } catch (Exception e) {
            e.printStackTrace();
            mMediaRecorder = null;
        }
    }

    private void startRecording() {
        mMediaRecorder.start();
        mCurrentVideoDuration = 0L;
        mainHandler.post(recordingDurationRun);
        isRecording = true;
        updateUI();
    }

    private void stopRecording() {
        try {
            mCurrentVideoDuration = 0L;
            mainHandler.removeCallbacks(recordingDurationRun);
            if (mMediaRecorder != null) {
                mMediaRecorder.stop();
                mMediaRecorder.release();
                mMediaRecorder = null;
                saveVideo();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        isRecording = false;
        updateUI();
    }

    private FileDescriptor getOutputFile() {
        mCurrentVideoUri = getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, createVideoContentValues(mConfigManager.videoSize.getWidth(), mConfigManager.videoSize.getHeight()));
        try {
            return getContentResolver().openFileDescriptor(mCurrentVideoUri, "rw").getFileDescriptor();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void closeCamera() {
        if (mMediaRecorder != null) {
            stopRecording();
        }
        if (mCameraCaptureSession != null) {
            mCameraCaptureSession.close();
            mCameraCaptureSession = null;
        }
        if (mCameraDevice != null) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
        if (mCaptureImageReader != null) {
            mCaptureImageReader.close();
            mCaptureImageReader = null;
        }
    }

    private void stopBackgroundThread() {
        if (backgroundThread != null) {
            backgroundThread.quitSafely();
            try {
                backgroundThread.join();
                backgroundThread = null;
                backgroundHandler = null;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void saveBitmap(Image image) {
        Log.d(TAG, "Ready to save picture");
        ByteBuffer buffer = image.getPlanes()[0].getBuffer();
        byte[] bytes = new byte[buffer.capacity()];
        buffer.get(bytes);
        ContentValues values = createImageContentValues(image.getWidth(), image.getHeight());
        final Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
        try {
            OutputStream out = getContentResolver().openOutputStream(uri);
            out.write(bytes);
            out.flush();
            out.close();
            Log.d(TAG, "The picture is save to your phone!");
        } catch (Exception e) {
            Log.e(TAG, "save picture Failed to write image,ex:", e);
        }
    }

    private void saveVideo() {
        Log.d(TAG, "Ready to save video");
        try {
            ContentValues values = new ContentValues();
            values.put(MediaStore.Video.Media.IS_PENDING, 0);
            long duration = getDuration();
            values.put(MediaStore.Video.Media.DURATION, duration);
            getContentResolver().update(mCurrentVideoUri, values, null, null);
            Log.d(TAG, "The video is save to your phone!");
            mCurrentVideoUri = null;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private long getDuration() {
        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
        try {
            retriever.setDataSource(this, mCurrentVideoUri);
            return Long.parseLong(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
        } catch (RuntimeException e) {
            return -1;
        } finally {
            retriever.release();
        }
    }

    private ContentValues createImageContentValues(int pictureWidth, int pictureHeight) {
        ContentValues values = new ContentValues();
        long dateTaken = System.currentTimeMillis();
        String title = new SimpleDateFormat(IMAGE_FORMAT, Locale.getDefault()).format(dateTaken);
        String mime = "image/jpeg";
        String fileName = title + ".jpg";
        values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, dateTaken);
        values.put(MediaStore.Images.ImageColumns.TITLE, title);
        values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, fileName);
        values.put(MediaStore.Images.ImageColumns.MIME_TYPE, mime);
        values.put(MediaStore.Images.ImageColumns.WIDTH, pictureWidth);
        values.put(MediaStore.Images.ImageColumns.HEIGHT, pictureHeight);
        // values.put(MediaStore.Images.ImageColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM + "/Camera/");
        Log.d(TAG, "createImageContentValues, width : " + pictureWidth + ", height = " +
                pictureHeight + ", dataTaken = " + dateTaken
                + ", fileName = " + fileName);
        return values;
    }

    private ContentValues createVideoContentValues(int videoWidth, int videoHeight) {
        ContentValues values = new ContentValues();
        long dateTaken = System.currentTimeMillis();
        String title = new SimpleDateFormat(VIDEO_FORMAT, Locale.getDefault()).format(dateTaken);
        String mime = "video/mp4";
        String fileName = title + ".mp4";
        values.put(MediaStore.Video.Media.TITLE, title);
        values.put(MediaStore.Video.Media.DISPLAY_NAME, title);
        values.put(MediaStore.Video.Media.DATE_TAKEN, dateTaken);
        values.put(MediaStore.Video.Media.MIME_TYPE, mime);
        values.put(MediaStore.Video.Media.WIDTH, videoWidth);
        values.put(MediaStore.Video.Media.HEIGHT, videoHeight);
        values.put(MediaStore.Video.Media.RESOLUTION, videoWidth + "x" + videoHeight);
        values.put(MediaStore.Video.Media.IS_PENDING, 1);
        // values.put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM + "/Camera/");
        Log.d(TAG, "createVideoContentValues, width : " + videoWidth + ", height = " +
                videoHeight + ", dataTaken = " + dateTaken
                + ", fileName = " + fileName);
        return values;
    }

    private void updateTextureSize() {
        if (mConfigManager.previewSize == null || mTextureView == null) return;
        int displayOrientation = CoordinatesTransform.getDisplayRotation(this);
        Log.d(TAG, "updateTextureSize: displayOrientation=" + displayOrientation);
        runOnUiThread(new Runnable() {
            @SuppressWarnings("all")
            @Override
            public void run() {
                ViewGroup parent = (ViewGroup) mTextureView.getParent();
                int width = parent.getWidth();
                int height = parent.getHeight();
                Log.d(TAG, "updateTextureSize: wh=" + width + " x " + height);
                if (displayOrientation == 0 || displayOrientation == 180) {
                    int temp = width;
                    width = height;
                    height = temp;
                }

                float aspectRatio = (float) mConfigManager.previewSize.getWidth() / mConfigManager.previewSize.getHeight();
                int targetWidth, targetHeight;
                targetWidth = height;
                targetHeight = width;

                aspectRatio = 1 / aspectRatio;
                int originalWidth = targetWidth;
                int originalHeight = targetHeight;
                int possibleHeight = (int) (originalWidth / aspectRatio);
                if (possibleHeight <= originalHeight) {
                    targetWidth = originalWidth;
                    targetHeight = possibleHeight;
                } else {
                    int possibleWidth = (int) (originalHeight * aspectRatio);
                    targetWidth = possibleWidth;
                    targetHeight = originalHeight;
                }

                ViewGroup.LayoutParams params = mTextureView.getLayoutParams();
                params.width = targetWidth;
                params.height = targetHeight;
                mTextureView.setLayoutParams(params);
                mTextureView.setRotation(TEXTURE_ORIENTATION.get(displayOrientation));
            }
        });
    }

    private void updateUI() {
        runOnUiThread(() -> {
            String[] cameraIdList;
            try {
                cameraIdList = mCameraManager.getCameraIdList();
                if (cameraIdList.length <= 1) {
                    btn_switch_camera.setVisibility(View.GONE);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (isVideoMode) {
                btn_switch_mode.setText(R.string.to_image);
                btn_is.setVisibility(View.GONE);
                btn_vs.setVisibility(View.VISIBLE);
                if (isRecording) {
                    recordingTime.setVisibility(View.VISIBLE);
                    btn_switch_camera.setEnabled(false);
                    btn_scene.setEnabled(false);
                    btn_wb.setEnabled(false);
                    btn_ps.setEnabled(false);
                    btn_vs.setEnabled(false);
                    btn_capture.setText(R.string.stop_video);
                } else {
                    recordingTime.setVisibility(View.GONE);
                    btn_switch_camera.setEnabled(true);
                    btn_scene.setEnabled(true);
                    btn_wb.setEnabled(true);
                    btn_ps.setEnabled(true);
                    btn_vs.setEnabled(true);
                    btn_capture.setText(R.string.start_video);
                }
            } else {
                recordingTime.setVisibility(View.GONE);
                btn_switch_mode.setText(R.string.to_video);
                btn_capture.setText(R.string.capture_image);
                btn_is.setVisibility(View.VISIBLE);
                btn_vs.setVisibility(View.GONE);
            }
        });
    }
}
